package com.xuan.yupao.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.xuan.yupao.common.ErrorCode;
import com.xuan.yupao.constant.PageConstant;
import com.xuan.yupao.constant.UserConstant;
import com.xuan.yupao.exception.BusinessException;
import com.xuan.yupao.mapper.FollowMapper;
import com.xuan.yupao.mapper.FriendMapper;
import com.xuan.yupao.mapper.UserMapper;
import com.xuan.yupao.model.domain.Follow;
import com.xuan.yupao.model.domain.Friend;
import com.xuan.yupao.model.domain.User;
import com.xuan.yupao.model.domain.request.UserUpdatePasswordRequest;
import com.xuan.yupao.model.domain.vo.UserVo;
import com.xuan.yupao.service.FollowService;
import com.xuan.yupao.service.FriendService;
import com.xuan.yupao.service.UserService;
import com.xuan.yupao.utils.AlgorithmUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.math3.util.Pair;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.xuan.yupao.constant.UserConstant.ADMIN_ROLE;
import static com.xuan.yupao.constant.UserConstant.USER_SESSION;

/**
 * @author 清凉茶
 * @description 针对表【user】的数据库操作Service实现
 * @createDate 2024-04-24 20:47:24
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Resource
    private UserMapper userMapper;

    @Resource
    private FollowMapper followMapper;

    @Resource
    private FriendMapper friendMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    private static final String SALT = "14e1b600b1fd579f47433b88e8d85291";

    /**
     * 用户注册
     *
     * @param userAccount   账户名
     * @param password      密码
     * @param checkPassword 校验密码
     * @return 用户自增主键
     */
    @Override
    public long userRegister(String userAccount, String password, String checkPassword, HttpServletRequest request) {
        // 校验账户名长度是否符合要求/是否包含特殊字符
        // 校验密码长度是否符合要求
        checkArgsIsValid(userAccount, password);

        // 检查密码和校验密码是否一致
        if (checkPassword.length() < 8 || !password.equals(checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码与确认密码不一致");
        }

        // 查看账户名是否存在
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_account", userAccount);
        if (this.count(queryWrapper) > 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账户名已存在");
        }

        // 对密码进行加密并向数据库插入数据
        String safetyPassword = DigestUtils.md5DigestAsHex((SALT + password).getBytes());
        User user = new User();
        user.setGender(0);
        user.setUserStatus(0);
        user.setCreateTime(new Date());
        user.setUpdateTime(new Date());
        user.setIsDelete(0);
        user.setUserRole(0);

        user.setUsername(userAccount);
        user.setUserAccount(userAccount);
        user.setUserPassword(safetyPassword);
        int index = RandomUtils.nextInt(0, 6);
        user.setAvatarUrl(UserConstant.AVATAR_URL_ARRAY[index]);
        user.setTags("");
        boolean saveResult = this.save(user);

        if (!saveResult) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "系统内部错误,用户注册失败");
        }
        request.getSession().setAttribute(USER_SESSION, user);
        return user.getId();
    }

    /**
     * 用户登录
     *
     * @param userAccount  账户名
     * @param userPassword 密码
     * @param request      存储用户登录成功后的会话信息
     * @return 脱敏后的用户信息
     */
    @Override
    public Long userLogin(String userAccount, String userPassword, HttpServletRequest request) {
        // 校验参数的合法性
        checkArgsIsValid(userAccount, userPassword);

        // 校验密码是否正确
        String safetyPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("user_account", userAccount);
        wrapper.eq("user_password", safetyPassword);
        User user = userMapper.selectOne(wrapper);
        if (user == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账户名或密码错误");
        }

        // 往 session 中存储用户信息
        request.getSession().setAttribute(USER_SESSION, user);
        return user.getId();

    }

    /**
     * 对用户信息进行脱敏
     *
     * @param user 完整用户信息
     * @return 脱敏后的用户信息
     */
    @Override
    public UserVo getSafetyUser(User user) {
        UserVo safetyUser = new UserVo();
        safetyUser.setId(user.getId());
        safetyUser.setUsername(user.getUsername());
        safetyUser.setUserAccount(user.getUserAccount());
        safetyUser.setGender(user.getGender());
        safetyUser.setPhone(user.getPhone());
        safetyUser.setEmail(user.getEmail());
        safetyUser.setAvatarUrl(user.getAvatarUrl());
        safetyUser.setCreateTime(user.getCreateTime());
        safetyUser.setUserRole(user.getUserRole());
        safetyUser.setTags(user.getTags());
        safetyUser.setProfile(user.getProfile());
        return safetyUser;
    }

    /**
     * 校验用户账户名和密码是否符合要求
     *
     * @param userAccount 账户名
     * @param password    密码
     * @return 验证结果
     */
    private void checkArgsIsValid(String userAccount, String password) {
        if (StringUtils.isAnyBlank(userAccount, password)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账户名或密码为空!");
        }
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账户名长度小于4");
        }
        if (password.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度小于8");
        }

        // 校验账户名是否包含特殊字符
        String regEx = "[ -_`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]|\n|\r|\t";
        Matcher matcher = Pattern.compile(regEx).matcher(userAccount);
        if (matcher.matches()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账户名包含特殊字符");
        }
    }


    /**
     * 按用户名搜索指定用户（若userName为null，则默认查询全量用户）
     *
     * @param username 用户名
     * @param session  用于验证用户是否为管理员
     * @return 用户列表
     */
    @Override
    public List<UserVo> searchUsers(String username, HttpSession session) {
        // 校验是否拥有管理员权限
        User loginUser = (User) session.getAttribute(USER_SESSION);
        if (loginUser == null || !loginUser.getUserRole().equals(ADMIN_ROLE)) {
            throw new BusinessException(ErrorCode.NOT_AUTH);
        }

        QueryWrapper<User> wrapper = new QueryWrapper<>();
        if (StringUtils.isNotBlank(username)) {
            wrapper.like("username", username);
        }
        List<User> userList = this.list(wrapper);

        // 对用户信息进行脱敏
        return userList.stream().map(this::getSafetyUser).collect(Collectors.toList());
    }

    /**
     * 删除指定用户
     *
     * @param id      用户id
     * @param session 用于验证用户是否为管理员
     * @return 是否删除成功
     */
    @Override
    public boolean deleteUser(Integer id, HttpSession session) {
        if (id == null || id < 0) {
            return false;
        }

        // 校验是否拥有管理员权限
        User user = (User) session.getAttribute(USER_SESSION);
        if (user == null || !user.getUserRole().equals(ADMIN_ROLE)) {
            throw new BusinessException(ErrorCode.NOT_AUTH);
        }

        return this.removeById(id);
    }


    /**
     * 根据指定标签搜索相关用户(符合所有标签的用户)
     *
     * @param tagNameList 标签列表
     * @return 相关用户
     */
    @Override
    public List<UserVo> searchUsersByTagNameList(List<String> tagNameList, int pageNum, User loginUser) {
        // 查询的标签为空,直接分页查询
        if (CollectionUtils.isEmpty(tagNameList)) {
            List<User> userList = this.page(new Page<>(pageNum, PageConstant.PAGE_SIZE)).getRecords();
            return getUserVoAndSetIsFollow(userList, loginUser);
        }

        // 方法一：利用 SQL 查询
        // 方法二：利用内存查询 (此处使用内存查询)
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.isNotNull("tags");
        if (loginUser != null) {
            queryWrapper.ne("id", loginUser.getId());
        }
        List<User> userList = userMapper.selectList(queryWrapper);

        Gson gson = new Gson();
        List<User> userVoList = userList.stream().filter(user -> {
            // 将 json数组 转换为Java集合
            Set<String> tmpTagNameSet = gson.fromJson(user.getTags(), new TypeToken<Set<String>>() {
            }.getType());
            // 防止 tmpTagNameSet 为空
            tmpTagNameSet = Optional.ofNullable(tmpTagNameSet).orElse(new HashSet<>());
            for (String tagName : tagNameList) {
                if (!tmpTagNameSet.contains(tagName)) return false;
            }
            return true;
        }).collect(Collectors.toList());

        // 按 pageNum 要求返回所需的数据
        List<User> res = new ArrayList<>();
        // 1) 结果集包含所有数据
        if (pageNum * PageConstant.PAGE_SIZE <= userVoList.size()) {
            res = userVoList.subList((pageNum - 1) * PageConstant.PAGE_SIZE, pageNum * PageConstant.PAGE_SIZE);
            // 2) 结果集包含部分数据
        } else if ((pageNum - 1) * PageConstant.PAGE_SIZE <= userVoList.size()) {
            res = userVoList.subList((pageNum - 1) * PageConstant.PAGE_SIZE, userVoList.size());
        }
        // 3) 所需数据超出结果集的数据量(res 依然为空)

        // 获取返回结果集中,所关注用户的 id, 使关注的用户 isFollow = true
        return getUserVoAndSetIsFollow(res, loginUser);
    }


    /**
     * 利用 SQL 查询
     *
     * @param tagNameList 多个标签名
     * @return 关联的用户
     */
    @Deprecated
    private List<User> searchUserByTagsBySQL(List<String> tagNameList) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        for (String tagName : tagNameList) {
            queryWrapper = queryWrapper.like("tag_name", tagName);
        }
        List<User> userList = userMapper.selectList(queryWrapper);
        return userList;
    }


    /**
     * 更新用户信息(只有管理员或本人能够更新)
     *
     * @param user      待更新的用户
     * @param loginUser 登录用户
     * @return 是否更新成功  0-失败  1-成功
     */
    @Override
    public Integer updateUser(User user, User loginUser) {
        if (loginUser == null || user == null || user.getId() < 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 1. 只有管理员或用户本人能够更新用户信息
        //    管理员可以更新任意用户的信息
        //    普通用户只能更新自己的信息
        user.setId(loginUser.getId());
        if (!(user.getId().equals(loginUser.getId()) || isAdmin(loginUser))) {
            throw new BusinessException(ErrorCode.NOT_AUTH);
        }

        // 2. 重新查询数据库中用户的信息,因为存储到 session 中的用户信息可能是过时的
        User oldUser = this.getById(user.getId());
        if (oldUser == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "用户不存在");
        }

        // 3. 校验是否存在要更新的信息
        if (!updateFieldIsExist(user, oldUser)) {
            return 0;
        }
        boolean result = this.updateById(user);
        if (result) return 1;
        return 0;
    }

    private boolean updateFieldIsExist(User user, User oldUser) {
        String username = user.getUsername();
        Integer gender = user.getGender();
        String phone = user.getPhone();
        String email = user.getEmail();
        String profile = user.getProfile();
        if (StringUtils.isAllEmpty(username, phone, email, profile) && gender == null) {
            return false;
        }
        if (gender != null && (gender < 0 || gender > 2)) return false;

        if (!Objects.equals(username, oldUser.getUsername())) return true;
        if (!Objects.equals(gender, oldUser.getGender())) return true;
        if (!Objects.equals(phone, oldUser.getPhone())) return true;
        if (!Objects.equals(email, oldUser.getEmail())) return true;
        if (!Objects.equals(profile, oldUser.getProfile())) return true;
        return false;
    }


    @Override
    public List<UserVo> recommendUsers(int pageNum, String username, User loginUser) {
        // 1. 当前存在推荐缓存,直接返回(redis缓存的数据是 isFollow 未经过处理的数据)
        ValueOperations<String, Object> opsForValue = redisTemplate.opsForValue();
        String redisKey = String.format("user:recommend:%s", pageNum);
        List<UserVo> resUserList = (List<UserVo>) opsForValue.get(redisKey);
        if (StringUtils.isBlank(username) && resUserList != null) {
            if (loginUser != null) {
                // 返回结果去除当前登录用户
                resUserList = resUserList.stream().filter(userVo -> !Objects.equals(userVo.getId(), loginUser.getId())).collect(Collectors.toList());
            }
            return resUserList;
        }


        // 2. 若不存在缓存则查询数据库,若此次查询没有指定 username,则将查出的数据存储到 redis 中
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(username), "username", username);
        List<User> userList = this.page(new Page<>(pageNum, PageConstant.PAGE_SIZE), queryWrapper).getRecords();
        // 对查询出的用户信息进行脱敏并存储到redis中
        List<UserVo> userVoList = userList.stream().map(this::getSafetyUser).collect(Collectors.toList());
        try {
            if (StringUtils.isBlank(username)) {
                opsForValue.set(redisKey, userVoList, 1, TimeUnit.HOURS);
            }
        } catch (Exception e) {
            log.error("redis set userRecommendKey error: ", e);
        }
        return userVoList;
    }

    @Override
    public User getLoginUser(HttpServletRequest request) {
        User user = (User) request.getSession().getAttribute(USER_SESSION);
        if (user == null || user.getId() < 1) {
            throw new BusinessException(ErrorCode.NOT_LOGIN);
        }
        return user;
    }


    @Override
    public List<UserVo> matchUserByQuickSort(int pageNum, String username, User loginUser) {
        Gson gson = new Gson();
        // 1. 用户未设置标签,随机推荐用户
        List<String> loginUserTagList = gson.fromJson(loginUser.getTags(), new TypeToken<List<String>>() {
        }.getType());
        if (CollectionUtils.isEmpty(loginUserTagList)) {
            return this.recommendUsers(RandomUtils.nextInt(1, 100), username, loginUser);
        }

        // 2. 若用户没有使用搜索功能,则先从 redis 中查询是否存在缓存
        if (StringUtils.isBlank(username)) {
            int num = (pageNum / PageConstant.PAGE_NUM_TIMES + 1) * PageConstant.PAGE_NUM_TIMES;
            // 每个存储到 redis 的缓存列表的元素个数是 5 * PAGE_SIZE
            String redisKey = String.format("yupao:user:match:%d", num);
            List<UserVo> userVoList = (List<UserVo>) redisTemplate.opsForValue().get(redisKey);
            // 此次查询命中缓存,直接从缓存返回数据即可
            if (userVoList != null) {
                pageNum = (pageNum - 1) % PageConstant.PAGE_NUM_TIMES;
                // 缓存不包含结果集
                if (pageNum * PageConstant.PAGE_SIZE > userVoList.size()) {
                    return new ArrayList<>();
                } else if ((pageNum + 1) * PageConstant.PAGE_SIZE <= userVoList.size()) {
                    // 缓存包含要查询的全部结果集
                    return userVoList.subList(pageNum * PageConstant.PAGE_SIZE, (pageNum + 1) * PageConstant.PAGE_SIZE);
                } else {
                    // 缓存包含部分结果集
                    return userVoList.subList(pageNum * PageConstant.PAGE_SIZE, userVoList.size());
                }
            }
        }

        // 3. 用户使用了搜索功能 或 此次查询没有命中缓存, 从数据库重新查询用户信息
        // 1) 查询数据库中的 tags字段 非空的记录
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(username), "username", username);
        queryWrapper.isNotNull("tags");
        // 2) 只查询需要的字段,节省空间和时间
        queryWrapper.select("tags", "id");
        List<User> userList = this.list(queryWrapper);

        // 4. 将所有用户 "标签的距离" 存储到一个 List 中,再对距离进行排序,取出需要的数据
        //    如果此次没有使用搜索功能,则将查询到的数据放入 redis 作为缓存
        // 1) 获取相差距离,添加到线性表中 Pair<Long, Integer> -> 用户id-相差距离
        List<Pair<Long, Integer>> distanceList = new ArrayList<>();
        for (User user : userList) {
            String tags = user.getTags();
            // 标签为空或为user为登录用户,不用进行比较
            if (StringUtils.isBlank(tags) || Objects.equals(user.getId(), loginUser.getId())) {
                continue;
            }

            List<String> userTagList = gson.fromJson(tags, new TypeToken<List<String>>() {
            }.getType());
            int distance = AlgorithmUtils.minDistance(loginUserTagList, userTagList);
            distanceList.add(new Pair<>(user.getId(), distance));
        }

        // 2) 按距离从低到高的列表
        distanceList.sort(Comparator.comparingInt(Pair::getValue));
        // 3) 取出需要的数据
        int start = (pageNum - 1) * PageConstant.PAGE_SIZE;
        int end = (pageNum + PageConstant.PAGE_NUM_TIMES - 1) * PageConstant.PAGE_SIZE;
        if (start > distanceList.size()) {
            return new ArrayList<>();
        } else if (end > distanceList.size()) {
            end = distanceList.size();
        }
        List<Long> userIdList = distanceList.subList(start, end).stream().map(Pair::getKey).collect(Collectors.toList());

        // 5. 查询数据库中用户信息
        queryWrapper = new QueryWrapper<>();
        queryWrapper.in("id", userIdList);
        // 按用户 id 收集成为一个 Map, 再通过遍历原先按距离从低到高的 idList, 通过 userId 取出对应用户,则该顺序为用户相似度从高到低
        Map<Long, List<User>> idUserMap = this.list(queryWrapper).stream().collect(Collectors.groupingBy(User::getId));
        List<UserVo> userVoList = new ArrayList<>();
        for (Long userId : userIdList) {
            // 按用户相似度从大到小添加 userVo(脱敏后的用户信息)
            UserVo userVo = new UserVo();
            BeanUtils.copyProperties(idUserMap.get(userId).get(0), userVo);
            userVoList.add(userVo);
        }

        // 6. 若用户没有使用搜索功能,则将数据存入 redis 中
        if (StringUtils.isBlank(username)) {
            int num = (pageNum / PageConstant.PAGE_NUM_TIMES + 1) * PageConstant.PAGE_NUM_TIMES;
            String saveKey = String.format("yupao:user:match:%d", num);
            ValueOperations<String, Object> opsForValue = redisTemplate.opsForValue();
            opsForValue.set(saveKey, userVoList, 1, TimeUnit.HOURS);
        }
        return userVoList;
    }

    @Override
    public UserVo getUserVoById(Long userId, User loginUser) {
        // 1. 获取用户信息并脱敏
        User user = this.getById(userId);
        if (user == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "用户不存在");
        }
        UserVo userVo = this.getSafetyUser(user);
        // 2. 若当前用户未登录 或 查询的用户是否为本人(是则直接返回)
        if (loginUser == null || Objects.equals(userId, loginUser.getId())) {
            return userVo;
        }
        // 3. 查询登录用户是否关注该用户
        Long loginUserId = loginUser.getId();
        QueryWrapper<Follow> followQueryWrapper = new QueryWrapper<>();
        followQueryWrapper.eq("user_id", userId);
        followQueryWrapper.eq("fan_id", loginUserId);
        long count = followMapper.selectCount(followQueryWrapper);
        userVo.setIsFollow(count > 0);
        // 4. 查询用户是否为登录用户的好友
        QueryWrapper<Friend> friendQueryWrapper = new QueryWrapper<>();
        friendQueryWrapper.eq("user_id", loginUserId);
        friendQueryWrapper.eq("friend_id", userId);
        count = friendMapper.selectCount(friendQueryWrapper);
        userVo.setIsFriend(count > 0);
        return userVo;
    }

    @Override
    public boolean updatePassword(UserUpdatePasswordRequest updatePasswordRequest, User loginUser) {
        String oldPassword = updatePasswordRequest.getOldPassword();
        String newPassword = updatePasswordRequest.getNewPassword();
        String confirmPassword = updatePasswordRequest.getConfirmPassword();
        // 1. 检查参数是否合法
        if (oldPassword.length() < 8 || newPassword.length() < 8 || confirmPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度应大于 8 位");
        }
        if (!Objects.equals(newPassword, confirmPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "新密码与确认密码不一致");
        }
        // 2. 查看数据库中用户加密后的密码与 oldPassword 是否一致
        User user = this.getById(loginUser.getId());
        oldPassword = DigestUtils.md5DigestAsHex((SALT + oldPassword).getBytes());
        if (!Objects.equals(oldPassword, user.getUserPassword())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "请输入正确的老密码");
        }
        // 3. 对新密码进行加密并与数据库中的密码对比
        newPassword = DigestUtils.md5DigestAsHex((SALT + newPassword).getBytes());
        if (Objects.equals(newPassword, user.getUserPassword())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "修改后的密码与原密码不能一样");
        }
        user.setUserPassword(newPassword);
        boolean b = this.updateById(user);
        if (!b) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "系统错误,修改密码失败");
        }
        return true;
    }

    @Override
    public boolean isAdmin(User user) {
        return user != null && user.getId() > 0 && user.getUserRole().equals(ADMIN_ROLE);
    }


    private List<UserVo> getUserVoAndSetIsFollow(List<User> userList, User loginUser) {
        // followIdList 为返回结果集中,当前登录用户关注的用户的id
        List<Long> followIdList;
        if (!CollectionUtils.isEmpty(userList) && loginUser != null) {
            // 查询是否关注了该用户
            QueryWrapper<Follow> followQueryWrapper = new QueryWrapper<>();
            followQueryWrapper.eq("fan_id", loginUser.getId());
            followQueryWrapper.in("user_id", userList.stream().map(User::getId).collect(Collectors.toList()));
            followIdList = followMapper.selectList(followQueryWrapper).stream().map(Follow::getUserId).collect(Collectors.toList());
        } else {
            followIdList = new ArrayList<>();
        }

        return userList.stream().map(user -> {
            UserVo userVo = this.getSafetyUser(user);
            if (followIdList.contains(user.getId())) {
                userVo.setIsFollow(true);
            }
            return userVo;
        }).collect(Collectors.toList());
    }


//    private List<UserVo> getUserVoAndSetIsFollow(User loginUser, List<UserVo> userVoList) {
//        Long loginUserId = loginUser.getId();
//        QueryWrapper<Follow> followQueryWrapper = new QueryWrapper<>();
//        followQueryWrapper.eq("fan_id", loginUserId);
//        followQueryWrapper.in("user_id", userVoList.stream().map(UserVo::getId).collect(Collectors.toList()));
//        List<Long> followIdList = followMapper.selectList(followQueryWrapper).stream().map(Follow::getUserId).collect(Collectors.toList());
//        return userVoList.stream().map(userVo -> {
//            if (followIdList.contains(userVo.getId())) {
//                userVo.setIsFollow(true);
//            }
//            return userVo;
//        }).collect(Collectors.toList());
//    }


    @Override
    @Deprecated
    public List<UserVo> matchUsersByHeap(int pageNum, String username, User loginUser) {
        Gson gson = new Gson();
        // 1. 用户未设置标签,随机推荐用户
        List<String> loginUserTagList = gson.fromJson(loginUser.getTags(), new TypeToken<List<String>>() {
        }.getType());
        if (CollectionUtils.isEmpty(loginUserTagList)) {
            return this.recommendUsers(RandomUtils.nextInt(1, 100), username, loginUser);
        }

        // 2. 若用户没有使用搜索功能,则先从 redis 中查询是否存在缓存
        if (StringUtils.isBlank(username)) {
            int num = (pageNum - 1) / PageConstant.PAGE_NUM_TIMES * PageConstant.PAGE_NUM_TIMES;
            // 每个存储到 redis 的缓存列表的元素个数是 5 * PAGE_SIZE
            String redisKey = String.format("user:match:%d", num);
            List<UserVo> userVoList = (List<UserVo>) redisTemplate.opsForValue().get(redisKey);
            // 此次查询命中缓存,直接从缓存返回数据即可
            if (userVoList != null) {
                pageNum %= PageConstant.PAGE_NUM_TIMES;
                // 缓存包含要查询的全部结果集
                if (pageNum * PageConstant.PAGE_SIZE <= userVoList.size()) {
                    return userVoList.subList((pageNum - 1) * PageConstant.PAGE_SIZE, pageNum * PageConstant.PAGE_SIZE);
                } else {
                    // 缓存包含部分结果集
                    return userVoList.subList((pageNum - 1) * PageConstant.PAGE_SIZE, userVoList.size());
                }
            }
        }

        // 3. 用户使用了搜索功能 或 此次查询没有命中缓存, 从数据库重新查询用户信息
        // 1) 查询数据库中的 tags字段 非空的记录
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(username), "username", username);
        queryWrapper.isNotNull("tags");
        // 2) 只查询需要的字段,节省空间和时间
        queryWrapper.select("tags", "id");
        List<User> userList = this.list(queryWrapper);

        // 4. 使用最小编辑距离算法查找 num 个和当前登录用户 tags(标签) 更加匹配的用户
        //    Top K 问题,查询相差距离最小的 num 个用户, 使用大根堆
        //                 用户id => 相差距离
        PriorityQueue<Pair<Long, Integer>> pq = new PriorityQueue<>((pa, pb) -> pb.getValue() - pa.getValue());
        for (User user : userList) {
            String tags = user.getTags();
            // 标签为空或为user为登录用户,不用进行比较
            if (StringUtils.isBlank(tags) || Objects.equals(user.getId(), loginUser.getId())) {
                continue;
            }

            List<String> userTagList = gson.fromJson(tags, new TypeToken<List<String>>() {
            }.getType());
            int distance = AlgorithmUtils.minDistance(loginUserTagList, userTagList);
            // TODO 从这里开始没做完
            //                             线性表的个数                          每个线性表包含 5 次结果集      每个结果集需要查询的记录数
            long num = (long) (pageNum / PageConstant.PAGE_NUM_TIMES + 1) * PageConstant.PAGE_NUM_TIMES * PageConstant.PAGE_SIZE;
            if (pq.peek() != null && (pq.size() < num || pq.peek().getValue() > distance)) {
                pq.offer(new Pair<>(user.getId(), distance));
            }
            if (pq.size() > num) pq.poll();
        }

        // 按距离从低到高的列表    用户id => 相差距离
        List<Long> userIdList = new ArrayList<>();
        while (!pq.isEmpty()) userIdList.add(pq.poll().getKey());
        Collections.reverse(userIdList);

        // 查询数据库中用户信息
        queryWrapper = new QueryWrapper<>();
        queryWrapper.in("id", userIdList);
        // 按用户 id 收集成为一个 Map, 再通过遍历原先按距离从低到高的 idList, 通过 userId 取出对应用户,则该顺序为用户相似度从高到低
        Map<Long, List<User>> idUserList = this.list(queryWrapper).stream().collect(Collectors.groupingBy(User::getId));
        List<UserVo> userVoList = new ArrayList<>();
        for (Long userId : userIdList) {
            // 按用户相似度从大到小添加 userVo(脱敏后的用户信息)
            UserVo userVo = new UserVo();
            BeanUtils.copyProperties(idUserList.get(userId).get(0), userVo);
            userVoList.add(userVo);
        }

        return userVoList;
    }
}




